const tool = require('../../tool')
const flow = require('./flow')
const throwNewError = require('./throwError')
const db = require('../db')
const path = require('path')
const ExcelJS = require('exceljs')
const fs = require('fs');
const crypto = require('crypto');
// const chalk = require('chalk')
const { createLogger, format, transports } = require('winston');

const logger = createLogger({
    level: 'info',
    format: format.combine(
        format.timestamp(),
        format.printf(({ timestamp, level, message }) => {
            return `${timestamp} ${level}: ${message}`;
        })
    ),
    transports: [
        new transports.Console(), // 这是将日志输出到控制台的传输器
        new transports.File({ filename: 'combined.log' })
    ]
});

// methods
const methods = {
    alert() { return require('../methods/alert'); },
    campuses() { return require('../methods/campuses'); },
    categories() { return require('../methods/categories'); },
    departments() { return require('../methods/departments'); },
    notification() { return require('../methods/notification'); },
    patrol() { return require('../methods/patrol'); },
    permissions() { return require('../methods/permissions'); },
    postWeChatMessage() { return require('../methods/postWechatMessage'); },
    sendMessage() { return require('../methods/sendMessage'); },
    settings() { return require('../methods/settings'); },
    system() { return require('../methods/system'); },
    user() { return require('../methods/user'); },
    vcode() { return require('../methods/vcode'); }
};

// modules
const modules = {
    notification() { return require('../modules/notification'); },
    patrol() { return require('../modules/patrol'); },
    setting() { return require('../modules/setting'); },
    system() { return require('../modules/system'); },
    user() { return require('../modules/user'); },
    campus() { return require('../modules/campus'); },
    category() { return require('../modules/category'); },
    admin() { return require('../modules/admin'); },
    test() { return require('../modules/test'); }
};

async function getChalk() {
    return await import('chalk')
}

module.exports = {
    cLog: {
        enable: true,
        async api(exp, method, auth, status, data = {}) {
            if (this.enable === false) return
            let chalk = await getChalk()
            if (status == 1) {
                return
                logger.info(`\t${chalk.default.yellow('接口开始')} ${chalk.default[method == 'get' ? 'red' : 'blue'](method == 'get' ? 'GET' : 'PST')} ${exp + (() => {
                    let re = ''
                    for (let i = 0; i < 20 - exp.length * 2; i++) {
                        re += ' '
                    }
                    return re
                })()}\t ${data.currentUserName === undefined ? '未登录' : data.currentUserName}${data.currentUserId === undefined ? '' : `(${data.currentUserId})`}`)
            }
            if (status == 2) {
                logger.info(`\t${chalk.default.green('接口成功')} ${chalk.default[method == 'get' ? 'red' : 'blue'](method == 'get' ? 'GET' : 'PST')} ${exp + (() => {
                    let re = ''
                    for (let i = 0; i < 20 - exp.length * 2; i++) {
                        re += ' '
                    }
                    return re
                })()}\t ${data.currentUserName === undefined ? '未登录' : data.currentUserName}${data.currentUserId === undefined ? '' : `(${data.currentUserId})`}`)
            }
            if (status == 0) {
                logger.info(`\t${chalk.default.red('接口失败')} ${chalk.default.red(method == 'get' ? 'GET' : 'PST')} ${exp + (() => {
                    let re = ''
                    for (let i = 0; i < 20 - exp.length * 2; i++) {
                        re += ' '
                    }
                    return re
                })()}\t ${data.currentUserName === undefined ? '未登录' : data.currentUserName}`)
            }
        },
        async runner(status) {
            return
            if (this.enable === false) return
            let chalk = await getChalk()
            if (status) {
                logger.info(`${chalk.default.yellow('runner开始')}`)
            } else {
                logger.info(`${chalk.default.red('runner停止')}`)
            }
        },
        async db(sql, status) {
            return
            if (this.enable === false) return
            // return 
            let chalk = await getChalk()
            if (status == 1) {
                logger.info(`\t\t${chalk.default.yellow('数据库开始')} ${sql}`)
            }
            if (status == 2) {
                logger.info(`\t\t${chalk.default.green('数据库成功')} ${sql}`)
            }
            if (status == 0) {
                logger.info(`\t\t${chalk.default.red('数据库错误')} ${sql}`)
            }
        },
        async dbCacheGet(sql, status) {
            return
            if (this.enable === false) return
            let chalk = await getChalk()
            if (status == 1) {
                logger.info(`\t\t${chalk.default.yellow('取缓存开始')} ${sql}`)
            }
            if (status == 2) {
                logger.info(`\t\t${chalk.default.green('取缓存成功')} ${sql}`)
            }
            if (status == 0) {
                logger.info(`\t\t${chalk.default.red('取缓存失败')} ${sql}`)
            }
        },
        async dbCacheAdd(sql) {
            return
            if (this.enable === false) return
            let chalk = await getChalk()
            logger.info(`\t\t${chalk.default.green('添加缓存')} ${sql}`)
        },
        async userLogin(userData) {
            let chalk = await getChalk()
            logger.info(`\t${chalk.default.bgGreenBright('用户登录')}     \t\t\t\t ${userData.username}(${userData.id})`)
        },
        async frontStatus(username, userId, content) {
            let chalk = await getChalk()
            logger.info(`\t${chalk.default.green('前端')} ${username} ${userId} ${chalk.default.bgBlueBright(content)}`)
        }
    },
    getChalk,
    logger,
    methods,
    modules,
    cachef() { return require('../tools/cache'); },
    db,
    dbf() {
        return require('../db')
    },
    array: {
        delete: {
            byIndex(arr = [], index = -1) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                if (check.isInt(index) == false) {
                    check.throwError('index应为整数')
                }
                return [...arr.slice(0, index), ...arr.slice(index + 1)]
            },
            byKeyValue(arr = [], key = '', value = undefined) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                return arr.filter(item => item[key] != value)
            },
            byValue(arr = [], value = undefined) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                return arr.filter(item => item != value)
            },
            clearUndefined(arr = []) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                return arr.filter(item => item != undefined)
            }
        },
        async format(arr = [], func = async (e) => { return e }) {
            if (check.isArray(arr) == false) {
                check.throwError('arr应为数组')
            }
            for (let i = 0; i < arr.length; i++) {
                arr[i] = await func(arr[i])
            }
            return arr
        },
        count(arr, value) {
            let re = 0
            for (let i = 0; i < arr.length; i++) {
                if (arr[i] === value) re++
            }
            return re
        },
        getFirstIndex: {
            byKeyValue(arr = [], key = '', value = undefined) {
                for (let i = 0; i < arr.length; i++) {
                    if (arr[i][key] === value) return i
                }
                return -1
            }
        }
    },
    object: {
        async for(obj = {}, func = async (key, value) => { return value }) {
            for (let i = 0; i < Object.keys(obj).length; i++) {
                obj[Object.keys(obj)[i]] = await func(Object.keys(obj)[i], obj[Object.keys(obj)[i]])
            }
            return obj
        },
        deleteUndefinedKeys(obj) {
            let re = []
            for (let i = 0; i < Object.keys(obj).length; i++) {
                if (obj[Object.keys(obj)[i]] === undefined) re.push(Object.keys(obj)[i])
            }
            for (let i = 0; i < re.length; i++)delete obj[re[i]]
            if(Object.keys(obj).length==0)return undefined
            return obj
        }
    },
    string: {
        delete: {
            bySubString(string, subString) {
                string = string.split(subString)
                let re = ''
                for (let i = 0; i < string.length; i++) {
                    re += string[i]
                }
                return re
            }
        }
    },
    JSON: {
        parse(string) {
            try {
                return JSON.parse(string)
            } catch (error) {
                return string
            }
        },
        stringify(obj) {
            return JSON.stringify(obj)
        }
    },
    sliceByPageSize(arr, page, size) {
        return arr.slice((page - 1) * size, page * size)
    },
    encodeString(string) {

        function isLetter(code) {
            if (code >= 65 && code <= 90) {
                return 1
            }
            if (code >= 97 && code <= 122) {
                return 1
            }
            if (code > 90 && code < 97) {
                return 2
            }
            if (code < 65) {
                return 0
            }
            return 2
        }

        function getNumberString(n, x) {
            n = JSON.stringify(n)
            for (let i = n.length; i < x; i++) {
                n = '0' + n
            }
            return n
        }

        string = string.split('')
        let string1 = ''
        for (let i = 0; i < string.length; i++) {
            string1 += JSON.stringify(JSON.stringify(string[i].charCodeAt(0)).length) + JSON.stringify(string[i].charCodeAt(0))
        }
        string1 = string1.split('')
        for (let i = 0; i < string1.length; i++) {
            if (string1[i] == '0') {
                continue
            }
            let c = i
            let ss = string1[c]
            while (true) {
                let cd = isLetter(parseInt(ss))
                if (cd == 2) {
                    break
                }
                if (cd == 1) {
                    string1[c] = String.fromCharCode(parseInt(ss))
                    for (let i1 = i; i1 < c; i1++) {
                        string1[i1] = '-'
                    }
                    i = c
                    break
                }
                c++
                if (c == string1.length) {
                    break
                }
                ss += string1[c]
            }
        }
        for (let i = 0; i < string1.length - 1; i++) {
            if (string1[i] == '2' && string1[i + 1] == '2') {
                string1[i] = '?'
                string1[i + 1] = '-'
            }
        }
        let string2 = []
        for (let i = 0; i < string1.length; i++) {
            if (string1[i] == '-') {
                continue
            }
            string2.push(JSON.stringify((string1[i].charCodeAt(0) - string1[i].charCodeAt(0) % 52) / 52) + getNumberString(string1[i].charCodeAt(0) % 52, 2))
        }
        let string3 = ''
        for (let i = string2.length - 1; i >= 0; i--) {
            string3 += string2[i]
        }
        string3 = string3.split('')
        let string4 = []
        for (let i = 0; i < string3.length; i += 2) {
            let ch = string3[i]
            if (i + 1 != string3.length) {
                ch += string3[i + 1]
            }
            if (parseInt(ch) < 52 && ch.length == 2) {
                if (parseInt(ch) < 26) {
                    string4.push(String.fromCharCode(parseInt(ch) + 65))
                } else {
                    string4.push(String.fromCharCode(parseInt(ch) + 97 - 26))
                }
            } else {
                string4.push(ch)
            }
        }
        let string5 = ''
        for (let i = 0; i < string4.length; i++) {
            string5 += string4[i]
        }
        return string5
    },
    decodeString(string) {
        function getNumberString(n, x) {
            n = JSON.stringify(n)
            for (let i = n.length; i < x; i++) {
                n = '0' + n
            }
            return n
        }

        string = string.split('')
        for (let i = 0; i < string.length; i++) {
            if (string[i].charCodeAt(0) >= 48 && string[i].charCodeAt(0) <= 56) {
                continue
            }
            if (string[i].charCodeAt(0) <= 90) {
                string[i] = getNumberString(string[i].charCodeAt(0) - 65, 2)
            } else {
                string[i] = getNumberString(string[i].charCodeAt(0) - 97 + 26, 2)
            }
        }
        let string2 = ''
        for (let i = 0; i < string.length; i++) {
            if (string[i] == '-8') {
                string2 += '9'
                continue
            }
            string2 += string[i]
        }
        string2 = string2.split('')
        let string3 = []
        for (let i = 0; i < string2.length; i += 3) {
            string3.push(getNumberString(parseInt(string2[i]) * 52 + parseInt(string2[i + 1] + string2[i + 2]), 3))
        }
        let string4 = []
        for (let i = string3.length - 1; i >= 0; i--) {
            string4.push(String.fromCharCode(parseInt(string3[i])))
        }
        let string5 = ''
        for (let i = 0; i < string4.length; i++) {
            if (string4[i] == '?') {
                string5 += '22'
                continue
            }
            if ((string4[i].charCodeAt(0) >= 48 && string4[i].charCodeAt(0) <= 57) == false) {
                string5 += JSON.stringify(string4[i].charCodeAt(0))
                continue
            }
            string5 += string4[i]
        }
        string5 = string5.split('')
        let string6 = ''
        for (let i = 0; i < string5.length; i++) {
            let n = parseInt(string5[i])
            let s = ''
            for (let i1 = 0; i1 < n; i1++) {
                s += string5[i + 1 + i1]
            }
            string6 += String.fromCharCode(parseInt(s))
            i += n
        }
        return string6
    },
    formatDate2(date) {
        const year = date.getFullYear();
        const month = (date.getMonth() + 1).toString().padStart(2, '0');  // 月份从0开始，+1调整
        const day = date.getDate().toString().padStart(2, '0');
        const hours = date.getHours().toString().padStart(2, '0');
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const seconds = date.getSeconds().toString().padStart(2, '0');

        return `${year}${month}${day}${hours}${minutes}${seconds}`;
    },
    formatDate3(date) {
        const year = date.getFullYear();
        const month = (date.getMonth() + 1).toString().padStart(2, '0');  // 月份从0开始，+1调整
        const day = date.getDate().toString().padStart(2, '0');
        const hours = date.getHours().toString().padStart(2, '0');
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const seconds = date.getSeconds().toString().padStart(2, '0');

        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    formatTimeNew(inputTime = new Date()) {
        inputTime = new Date(inputTime)
        function getPeriod(hours) {
            if (hours >= 0 && hours < 6) {
                return '凌晨';
            } else if (hours >= 6 && hours < 11) {
                return '上午';
            } else if (hours >= 11 && hours < 14) {
                return '中午';
            } else if (hours >= 14 && hours < 18) {
                return '下午';
            } else {
                return '晚上';
            }
        }

        function getWeekName(n) {
            switch (n) {
                case 0:
                    return '星期天'
                case 1:
                    return '星期一'
                case 2:
                    return '星期二'
                case 3:
                    return '星期三'
                case 4:
                    return '星期四'
                case 5:
                    return '星期五'
                case 6:
                    return '星期六'
            }
        }
        let currentTime = new Date()
        let oneDaySecond = 86400000
        let currentDayStart = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000)
        let lastDayStart = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000 - oneDaySecond)
        let nextDayStart = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000 + oneDaySecond)
        let nextDayEnd = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000 + oneDaySecond * 2)
        let prefix = ''
        if (inputTime >= currentDayStart && inputTime < nextDayStart) {
            prefix = '今天'
        }
        if (inputTime >= lastDayStart && inputTime < currentDayStart) {
            prefix = '昨天'
        }
        if (inputTime >= nextDayStart && inputTime < nextDayEnd) {
            prefix = '明天'
        }
        if (prefix == '') {
            prefix = (inputTime.getFullYear() == currentTime.getFullYear() ? '' : inputTime.getFullYear().toString() +
                '年') + (inputTime.getMonth() + 1).toString() + '月' + inputTime.getDate() + '日 '
        }
        prefix += getPeriod(inputTime.getHours()) + ' '
        prefix += inputTime.getHours().toString() + ':'
        if (inputTime.getMinutes() < 10) {
            prefix += '0'
        }
        prefix += inputTime.getMinutes().toString() + ' '
        prefix += getWeekName(inputTime.getDay())
        return prefix
    },
    wait(n) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                return resolve()
            }, n * 1000);
        })
    },
    flow,
    throwNewError,
    async deleteFile(filename) {
        const path = require('path')
        const fs = require('fs')
        const filePath = path.join(__dirname, '../../files', filename)
        try {
            const fileExists = await new Promise((resolve, reject) => {
                fs.access(filePath, fs.constants.F_OK, (err) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(true)
                    }
                })
            })
            if (fileExists) {
                await new Promise((resolve, reject) => {
                    fs.unlink(filePath, (err) => {
                        if (err) {
                            reject(err);
                        } else {
                            resolve();
                        }
                    });
                });
                // console.log(`文件 ${filename} 已被删除`);
            } else {
                console.log(`文件 ${filename} 不存在`);
            }
        } catch (error) {
            console.error(`删除文件时出错: ${error}`);
        }
    },
    sendMessage(phoneNumber, content) {
        let prefix = '巡查e速办  '
        let xml1 = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sms="http://sms.webservice.com">   <soapenv:Header/>   <soapenv:Body>      <sms:saveSmsInfo>         <sms:in0>'
        let xml2 = '</sms:in0>      </sms:saveSmsInfo>   </soapenv:Body></soapenv:Envelope>'
        let args = {
            tp_name: 'cwxt',
            sys_id: 'mp',
            module_id: 'sms',
            secret_key: 'gCr1JkOuZfvnpJLxzBjDtNo/woQ=',
            interface_method: 'sms',
            person_info: '||||' + phoneNumber,
            sms_info: prefix + ' ' + content,
            send_priority: '3',
            operator_id: '管理员',
            operator_id_number: '09901',
            operator_unit_id: '1001016',
            operator_unit_name: '网络信息中心',
            templet_id: '0',
            receipt_id: '0',
            send_sys_id: '1',
            send_sys_name: '财务管理系统',
            user_browser: 'FireFox'
        }
        const axios = require('axios')
        axios({
            method: 'post',
            url: 'http://vp.lcu.edu.cn/tp_mp/service/SmsService', // 替换为实际的接口地址  
            data: xml1 + JSON.stringify(args) + xml2,
            headers: {
                'Content-Type': 'text/xml;charset=UTF-8',
                'SOAPAction': '',
            }
        })
            .then(response => {
                console.log(`短信发送成功`, phoneNumber, content)
                // clog('> 发短信', undefined, content, '成功', phoneNumber)
            })
            .catch(error => {

                // clog('> 发短信', undefined, content, '失败', phoneNumber)
            });
    },
    deepCopy(o) {
        return this.JSON.parse(this.JSON.stringify(o))
    },
    formatDateString(inputDate) {
        const date = new Date(inputDate);
        const now = new Date();
        const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        const diffDays = Math.round((target - today) / (1000 * 60 * 60 * 24));

        if (diffDays === 2) return "后天";
        if (diffDays === 1) return "明天";
        if (diffDays === 0) return "今天";
        if (diffDays === -1) return "昨天";
        if (diffDays === -2) return "前天";

        const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
        const targetWeekday = weekdays[date.getDay()];

        const oneWeekAgo = new Date();
        oneWeekAgo.setDate(today.getDate() - 7);

        const oneWeekLater = new Date();
        oneWeekLater.setDate(today.getDate() + 7);

        // if (target <= oneWeekAgo && target < today) return `上${targetWeekday}`;
        // if (target > today && target <= oneWeekLater) return targetWeekday;

        const year = date.getFullYear();
        const month = date.getMonth() + 1;
        const day = date.getDate();

        if (year === now.getFullYear()) {
            return `${month}月${day}日`;
        } else {
            return `${year}年${month}月${day}日`;
        }
    },
    formatTimeDifference(time1, time2) {
        let str = ''

        let month = 0
        let week = 0
        let day = 0
        let hour = 0
        let minute = 0

        let diff = Math.abs(new Date(time1) - new Date(time2)); // 计算时间差的绝对值

        let one_month = 1000 * 60 * 60 * 24 * 30
        let one_week = 1000 * 60 * 60 * 24 * 7
        let one_day = 1000 * 60 * 60 * 24
        let one_hour = 1000 * 60 * 60
        let one_minute = 1000 * 60

        month = parseInt(diff / one_month)
        diff -= one_month * month

        week = parseInt(diff / one_week)
        diff -= one_week * week

        day = parseInt(diff / one_day)
        diff -= one_day * day

        hour = parseInt(diff / one_hour)
        diff -= one_hour * hour

        minute = parseInt(diff / one_minute)
        diff -= one_minute * minute
        let re = `${month ? `${month}月` : ''}${week ? `${week}周` : ''}${day ? `${day}日` : ''}${hour ? `${hour}时` : ''}${minute ? `${minute}分钟` : ''}`
        return re == '' ? '1分钟内' : re

        // console.log(diff)

        let minutes = Math.floor(diff / (1000 * 60)) % 60;
        let hours = Math.floor(diff / (1000 * 60 * 60)) % 24;
        let days = Math.floor(diff / (1000 * 60 * 60 * 24)) % 7;
        let weeks = Math.floor(diff / (1000 * 60 * 60 * 24 * 7)) % 4;
        let months = Math.floor(diff / (1000 * 60 * 60 * 24 * 30));

        let result = [];
        if (months > 0) result.push(`${months}月`);
        if (weeks > 0) result.push(`${weeks}周`);
        if (days > 0) result.push(`${days}天`);
        if (hours > 0) result.push(`${hours}小时`);
        if (minutes > 0) result.push(`${minutes}分钟`);

        return result.length ? result.join('') : '0分钟';
    },
    async getAccessToken() {
        const { appId, appSecret } = require('../../config')
        const axios = require('axios')
        const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
        const response = await axios.get(url);
        return response.data.access_token;
    },
    async sendSubscribeMessage(openId, content, title, time, username, data = {}) {
        // console.log(openId, content, title, time, username, data = {})
        const axios = require('axios');
        const url = `https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${await this.getAccessToken()}`;

        const payload = {
            touser: openId, // 用户的 openid
            template_id: 'uK_iS4p6Yre6pyg_JpiUcxM0xiSJ88QEOPGrW3t95zU', // 模板 ID，直接写死或从 config 读取
            page: 'pages/index/index?' + require('querystring').stringify(data), // 点击消息跳转的页面（可选）
            data: {
                thing1: { value: content },  // 提醒内容
                thing2: { value: title },    // 作业名称
                time3: { value: tool.formatDateTime(new Date(time)) }, // 截至日期
                thing4: { value: username }  // 发布人员（修正字段名）
            }
        };

        try {
            const response = await axios.post(url, payload);
            return response.data;
        } catch (error) {
            return { errcode: -1, errmsg: '请求失败' };
        }
    },
    log() {

    },
    addTopTitleToArr(re) {
        for (let i = 0; i < re.length; i++) {
            if (!i || this.formatDateString(re[i].createdAt) != this.formatDateString(re[i - 1].createdAt)) {
                re[i].topTitle = this.formatDateString(re[i].createdAt)
            }
        }
        return re
    },
    getDeletedUserData() {
        return {
            username: '账号已注销',
            role: '',
            departmentInfo: {
                name: '未知'
            }
        }
    },
    async exportDataToExcel(data, fields, imageCallback) {
        const sizeOf = require('image-size'); // 引入 image-size 库
        // 创建工作簿和工作表
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet('Sheet1');

        // 设置表头，使用每个字段的 label 或 name
        const header = fields.map(field => field.label || field.name);
        worksheet.addRow(header);

        // 添加数据行（图片字段先写入图片文件名，后续插入图片）
        data.forEach(rowData => {
            const rowValues = fields.map(field => {
                return field.type === 'image' ? (rowData[field.name] || '') : rowData[field.name];
            });
            worksheet.addRow(rowValues);
        });

        // 定义正方形单元格大小（单位：点，1 点约等于 1/72 英寸）
        const cellSize = 40; // 可根据需要调整

        // 对于图片字段，逐行逐列处理
        // 注意：ExcelJS 行和列均为 1 索引，第一行为表头，所以数据从第二行开始
        for (let rowIndex = 2; rowIndex <= worksheet.rowCount; rowIndex++) {
            // await this.wait(0.1)
            const row = worksheet.getRow(rowIndex);
            for (let colIndex = 1; colIndex <= fields.length; colIndex++) {
                const field = fields[colIndex - 1];
                if (field.type === 'image') {
                    const fileName = row.getCell(colIndex).value;
                    if (fileName) {
                        try {
                            // 调用回调函数获取图片 Buffer
                            const imageBuffer = await imageCallback(fileName);
                            // 根据文件名获取扩展名，默认 png
                            const ext = path.extname(fileName).slice(1) || 'png';

                            // 获取图片的原始尺寸
                            const dimensions = sizeOf(imageBuffer);
                            const imgWidth = dimensions.width;
                            const imgHeight = dimensions.height;

                            // 计算缩放比例，使图片在不超出单元格正方形内时最大显示
                            const ratio = Math.min(cellSize / imgWidth, cellSize / imgHeight);
                            const finalWidth = imgWidth * ratio;
                            const finalHeight = imgHeight * ratio;

                            // 计算居中偏移量
                            const offsetX = (cellSize - finalWidth) / 2;
                            const offsetY = (cellSize - finalHeight) / 2;

                            // 将图片添加到工作簿中，获得图片 id
                            const imageId = workbook.addImage({
                                buffer: imageBuffer,
                                extension: ext
                            });

                            // 使用 addImage 方法插入图片到单元格中，指定 tl (top-left) 位置和图片显示尺寸
                            worksheet.addImage(imageId, {
                                tl: { col: colIndex - 1, row: rowIndex - 1, offsetX, offsetY },
                                ext: { width: finalWidth, height: finalHeight }
                            });

                            // 设置当前行高度为正方形（单位为点）
                            row.height = cellSize;
                            // 设置所在列宽为对应的字符宽（ExcelJS 的列宽单位不是点，通常约为 点数/7）
                            const approxColWidth = cellSize / 7;
                            worksheet.getColumn(colIndex).width = approxColWidth;
                        } catch (error) {
                            console.error(`添加图片 [${fileName}] 到行 ${rowIndex} 列 ${colIndex} 时出错:`, error);
                        }
                    }
                }
            }
        }

        return workbook;
    },
    async saveWorkbookToFile(workbook) {
        // files 文件夹路径（相对于当前脚本所在目录）
        const folderPath = path.join(__dirname, '../../files');

        // 如果文件夹不存在，则创建该文件夹
        if (!fs.existsSync(folderPath)) {
            fs.mkdirSync(folderPath, { recursive: true });
        }

        let fileName;
        let filePath;

        // 生成随机文件名，并确保文件名不重复
        do {
            fileName = crypto.randomBytes(16).toString('hex') + '.xlsx';
            filePath = path.join(folderPath, fileName);
        } while (fs.existsSync(filePath));

        // 使用 ExcelJS 的 writeFile 方法保存文件
        await workbook.xlsx.writeFile(filePath);

        return fileName;
    },
    getPatrolStatusName(status) {
        if (status == 1) return '等待'
        if (status == 2) return '延期'
        if (status == 3) return '完成'
        if (status == 4) return '满意度调查中'
        if (status == 5) return '拒绝'
    },
    getExportToExcelFields() {
        return [
            { name: 'id', label: '编号', type: 'text' },
            { name: 'createdAt', label: '创建时间', type: 'text' },
            { name: 'username', label: '用户名', type: 'text' },
            { name: 'phone', label: '电话', type: 'text' },
            { name: 'account', label: '账号', type: 'text' },
            { name: 'role', label: '角色', type: 'text' },
            { name: 'departmentName', label: '部门名称', type: 'text' },
            { name: 'desc', label: '描述', type: 'text' },

            { name: 'image1', label: '图片1', type: 'text' },
            { name: 'image2', label: '图片2', type: 'text' },
            { name: 'image3', label: '图片3', type: 'text' },
            { name: 'image4', label: '图片4', type: 'text' },
            { name: 'image5', label: '图片5', type: 'text' },

            { name: 'location', label: '位置', type: 'image' },
            { name: 'campus', label: '校区', type: 'text' },
            { name: 'category', label: '类别', type: 'text' },
            { name: 'status', label: '状态', type: 'text' },
            { name: 'endTime', label: '结束时间', type: 'text' },

            { name: 'delayCount', label: '延迟次数', type: 'text' },
            { name: 'delaySizeTotal', label: '延迟总时长', type: 'text' },
            // 假设最多存在3条延迟信息
            { name: 'delay1Info', label: '延迟信息1', type: 'text' },
            { name: 'delay2Info', label: '延迟信息2', type: 'text' },
            { name: 'delay3Info', label: '延迟信息3', type: 'text' },

            // finalHandle 部分
            { name: 'finalHandle_username', label: '最终处理人姓名', type: 'text' },
            { name: 'finalHandle_phone', label: '最终处理人电话', type: 'text' },
            { name: 'finalHandle_account', label: '最终处理人账号', type: 'text' },
            { name: 'finalHandle_role', label: '最终处理人角色', type: 'text' },
            { name: 'finalHandle_departmentName', label: '最终处理人部门', type: 'text' },
            { name: 'finalHandle_createdAt', label: '最终处理时间', type: 'text' },
            { name: 'finalHandle_desc', label: '最终处理描述', type: 'text' },
            { name: 'finalHandle_reject', label: '最终处理驳回原因', type: 'text' },
            { name: 'finalHandle_image1', label: '最终处理图片1', type: 'text' },
            { name: 'finalHandle_image2', label: '最终处理图片2', type: 'text' },
            { name: 'finalHandle_image3', label: '最终处理图片3', type: 'text' },
            { name: 'finalHandle_image4', label: '最终处理图片4', type: 'text' },
            { name: 'finalHandle_image5', label: '最终处理图片5', type: 'text' },

            // finalReview 部分
            { name: 'finalReview_username', label: '最终审核人姓名', type: 'text' },
            { name: 'finalReview_phone', label: '最终审核人电话', type: 'text' },
            { name: 'finalReview_account', label: '最终审核人账号', type: 'text' },
            { name: 'finalReview_role', label: '最终审核人角色', type: 'text' },
            { name: 'finalReview_departmentName', label: '最终审核人部门', type: 'text' },
            { name: 'finalReview_createdAt', label: '最终审核时间', type: 'text' },
            { name: 'finalReview_desc', label: '最终审核描述', type: 'text' },

            // finalFeedBack 部分
            { name: 'finalFeedBack_username', label: '最终评价人姓名', type: 'text' },
            { name: 'finalFeedBack_phone', label: '最终评价人电话', type: 'text' },
            { name: 'finalFeedBack_account', label: '最终评价人账号', type: 'text' },
            { name: 'finalFeedBack_role', label: '最终评价人角色', type: 'text' },
            { name: 'finalFeedBack_departmentName', label: '最终评价人部门', type: 'text' },
            { name: 'finalFeedBack_createdAt', label: '最终评价时间', type: 'text' },
            { name: 'finalFeedBack_desc', label: '最终评价描述', type: 'text' },
            { name: 'finalFeedBack_rating', label: '最终评价评分', type: 'text' },

            { name: 'num_handle', label: '处理次数', type: 'text' },
            { name: 'num_review', label: '审核次数', type: 'text' },
            { name: 'num_feedback', label: '评价次数', type: 'text' }
        ]
    },
    async generateQRCode(text) {
        const QRCode = require('qrcode')
        try {
            return await QRCode.toDataURL(text, { width: 300, margin: 2 });
        } catch (err) {
            console.error('生成二维码失败:', err);
            return null;
        }
    },
    async readImage(fileName) {
        try {
            const filePath = path.resolve(__dirname, '../../files', fileName);
            return await fs.promises.readFile(filePath);  // 使用 promises.readFile 替代 fs.readFile
        } catch (error) {
            console.error(`读取文件失败: ${fileName}`, error);
            throw error; // 让调用者处理错误
        }
    },
    async fetchImageBuffer(imageUrl) {
        try {
            const axios = require('axios')
            const response = await axios.get(imageUrl, {
                responseType: 'arraybuffer' // 以二进制流的形式接收数据
            });
            return Buffer.from(response.data);
        } catch (error) {
            console.error(`下载图片失败: ${imageUrl}`, error);
            throw error; // 让调用者处理错误
        }
    },
    formatPercentNum(num) {
        return parseInt(num * 10000) / 100
    },
    replaceSubstring(str, substr1, substr2) {
        // 使用正则表达式替换所有出现的子字符串1
        const regex = new RegExp(substr1, 'g');
        return str.replace(regex, substr2);
    },

}

const check = {
    isArray(e) {
        return Array.isArray(e)
    },
    isInt(e) {
        return (typeof (e) == 'number') && (parseInt(e) == e)
    },
    isNumber(e) {
        return (typeof (e) == 'number')
    },
    isString(e) {
        return typeof (e) == 'string'
    },
    throwError(reason) {
        throw new Error(reason)
    }
}